﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Xeralabs.Common
{
    public abstract class Pool<TObj> : IDisposable
    {
        /// <summary>
        /// Contiene los objetos actualmente en la reserva de memoria.
        /// </summary>
        protected List<TObj> pool;
        private int _nbrObjects = 0;
        private int _maxSize = 5;
        private bool _disposed;

        /// <summary>
        /// Devuelve el numero de objetos en la reserva de memoria.
        /// </summary>
        public int Count
        {
            get { return this.pool.Count; }
        }
        /// <summary>
        /// Devuelve el tamaño maximo de la reserva de memoria.
        /// </summary>
        public int MaxSize
        {
            get { return this._maxSize; }
        }
        /// <summary>
        /// Devuelve el numero de objetos en memoria.
        /// </summary>
        public int TotalObjects
        {
            get { return this._nbrObjects; }
        }

        /// <summary>
        /// Constructor por defecto.
        /// </summary>
        public Pool()
        {
            this.pool = new List<TObj>(this._maxSize);
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="maxSize">El tamaño maximo de la reserva de memoria.</param>
        public Pool(int maxSize)
        {
            this._maxSize = maxSize;
            this.pool = new List<TObj>(this._maxSize);
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="maxSize">El tamaño maximo de la reserva de memoria.</param>
        /// <param name="fill">Indica si deben iniciarse todos los objetos de la reserva de memoria.</param>
        public Pool(int maxSize, bool fill)
        {
            this._maxSize = maxSize;
            this.pool = new List<TObj>(this._maxSize);
            if (fill)
                this.Fill();
        }
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="maxSize">El tamaño maximo de la reserva de memoria.</param>
        /// <param name="initialObjects">Indica el numero inicial de objetos en la reserva de memoria. 0 o un numero negativo
        /// no crearan ningun objeto, un numero mayor que <see cref="maxSize"/> inicializa la reserva de memoria al maximo de
        /// su capacidad.</param>
        public Pool(int maxSize, int initialObjects)
        {
            this._maxSize = maxSize;
            this.pool = new List<TObj>(this._maxSize);
            if (initialObjects > 0)
                this.Fill(initialObjects);
        }

        // Always allows for unmanaged resource disposing.
        ~Pool()
        {
            Dispose(false);
        }

        /// <summary>
        /// Rellena la reserva de memoria hasta alcanzar el tamaño maximo de la reserva.
        /// </summary>
        public void Fill()
        {
            this.Fill(this._maxSize - _nbrObjects);
        }

        /// <summary>
        /// Rellena la reserva de memoria con un numero de objetos determinado por el parametro <see cref="nbrNewObjects"/>, hasta alcanzar el tamaño maximo de la reserva.
        /// </summary>
        /// <param name="nbrNewObjects">Indica el numero de objetos a añadir a la reserva de memoria.</param>
        public void Fill(int nbrNewObjects)
        {
            lock (pool)
            {
                if (this._nbrObjects + nbrNewObjects > this._maxSize)
                    nbrNewObjects = this._maxSize - this._nbrObjects;

                for (int i = 0; i < nbrNewObjects; i++)
                {
                    this.pool.Add(CreateNewPoolObject());
                    this._nbrObjects++;
                }
            }
        }

        /// <summary>
        /// Metodo abstracto para la creacion de un objeto del tipo generico <see cref="TObj"/>.
        /// </summary>
        /// <returns>Un nuevo objeto del tipo generico <see cref="TObj"/>.</returns>
        protected abstract TObj CreateNewPoolObject();

        /// <summary>
        /// Devuelve un objeto de la reserva de memoria. Si la reserva de memoria esta vacio, se crea un nuevo objeto, hasta el tamaño maximo permitido.
        /// Si se ha alcanzado el tamaño maximo, la solicitud queda en espera hasta que se libera un objeto.
        /// </summary>
        /// <returns>Un objeto del tipo generico <see cref="TObj"/>, extraido de la reserva de memoria.</returns>
        public TObj GetObject()
        {
            lock (pool)
            {
                TObj obj;
                if (pool.Count == 0 && _nbrObjects < this._maxSize) // no hay objetos pero queda espacio para nuevos.
                {
                    obj = CreateNewPoolObject();
                    _nbrObjects++;
                }
                else
                {
                    // Si no hay objetos, debemos esperar a que se libere al menos uno.
                    while (pool.Count == 0)
                        Monitor.Wait(pool);

                    obj = pool[pool.Count - 1];   // mas rapido al final.
                    pool.RemoveAt(pool.Count - 1);
                }
                return obj;
            }
        }

        /// <summary>
        /// Libera un objeto en uso y lo situa en la reserva de memoria.
        /// </summary>
        /// <param name="obj">El objeto en uso que va a ser liberado.</param>
        public virtual void ReleaseObject(TObj obj)
        {
            lock (pool)
            {
                pool.Add(obj);
                Monitor.Pulse(pool);
            }
        }


        #region Miembros de IDisposable

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        // The bulk of the clean-up code is implemented in Dispose(bool)
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                // allow objects to free unmanaged resources
                if (typeof(TObj) is IDisposable)
                    if (pool != null && pool.Count > 0)
                        foreach (TObj obj in pool)
                            ((IDisposable)obj).Dispose();

                if (disposing)
                {
                    // free managed resources
                    for (int a = 0; a < pool.Count; a++)
                        pool[a] = default(TObj);
                    pool.Clear();
                    pool = null;
                }

                _disposed = true;
            }
        }
    }
}
